/*
 * 2021/12/18	01:10	qing	
 */

/*
 * RtIInitializeGenericTable	P169
 */
	mov edi, edi
	push ebp
	mov ebp, esp
	mov eax, dword ptr ss:[ebp+8]  # 第一个参数 --> eax
	xor edx, edx                   # 清零。机器码比指令mov edx, 0 的机器码要短。
								   # 优化编译器为了减少代码体积和加快运行速度牺牲可读性。

	lea ecx, dword ptr ds:[eax+4]  # 加载有效地址load effective address. 
								   # 只是一条纯算术运算指令，它不执行任何实际的内存访问，等价于ECX=EAX+4.
	mov dword ptr ds:[eax], edx    #               eax     = edx
	mov dword ptr ds:[ecx+4], ecx  # eax + 4 + 4 = eax + 8 = ecx
	mov dword ptr ds:[ecx], ecx    # ecx =         eax + 4 = ecx
	mov dword ptr ds:[eax+c], ecx  #               eax + c = ecx

	mov ecx, dword ptr ss:[ebp+c]  # ecx = ebp +c --> 第二个参数 
	mov dword ptr ds:[eax+18], ecx # eax + 18 = ecx = 第二个参数
	mov ecx, dword ptr ss:[ebp+10] # 第三个参数
	mov dword ptr ds:[eax+1c], ecx

	mov ecx, dword ptr ss:[ebp+14] # 第四个参数
	mov dword ptr ds:[eax+20], ecx
	mov ecx, dword ptr ss:[ebp+18] # 第五个参数
	mov dword ptr ds:[eax+14], edx # edx = 0
	mov dword ptr ds:[eax+10], edx # edx = 0
	mov dword ptr ds:[eax+24], ecx # ecx = 第五个参数    

	pop ebp
	ret 14

/*
 * 分析
 */
1.ret 14

    0x14 = 20. 32bit(4字节)对齐，可以假定总共接收5个参数。当然也有可能其中有一个参数的长度超过4字节，
	这种情况下函数接收就会少于5个，但是参数个数绝对不会多于5个的，因为参数是32位对齐的。

2.EBP, stack frame
    EBP 的当前值被保存在堆栈上，而EBP中存储的是ESP的值。这样在函数运行过程中不管ESP的值如何变化，
	都可以用EBP来便捷地访问参数（只要这个函数在调用其他函数时向堆栈中压入参数ESP的值就会改变）。
	在这种常用的堆栈布局方法中，第一个参数放在 [EBP+8] 处，第二个参数放在 [EBP+C] 处。

	函数还会在堆栈中位局部变量分配存储空间，这时，ESP 又会减去存储局部变量所需的字节数。

3.SS：
    堆栈地址前面加有"SS:",这表明该地址要用堆栈段寄存器SS来读取。
	IA-32处理器支持专用的分段（segments）内存管理模式，但在windows中并没有用这种分段管理模式，
	在大多数情况下，可以放心忽略掉它们。CS，DS, FS，SS。FS允许访问线程局部内存(thread-local memory)中一个小的空间。

4.
	mov dword ptr ds:[eax], edx   # 将这个数据结构的第一个成员设置位0（用EDX）。这个结构是通过EAX访问的。
	mov dword ptr ds:[ecx+4], ecx # 将这个数据结构的第三个成员设置为这个数据结构的第二个成员的
								  # 地址（就是存储在"ECX：EAX+4"中的值）。这次这个数据结构是通过ECX而不是通过EAX访问的；
	mov dword ptr ds:[ecx], ecx   # 把第二个成员设置为（存储在ECX中）的那个地址；
	mov dword ptr ds:[eax+c], ecx # 把第四个成员设置为（存储在ECX中）的那个地址；

		这个函数保留了两个指向这个数据结构的指针，
		即EAX, ECX. EAX 存储的是通过第一个参数传来的原始值，而ECX存储的是地址"EAX+4"。
		该数据结构中的成员有的是通过EAX访问的，有的是通过ECX来访问的。

	翻译成C语言代码：
		unkownstruct->member1 = 0;
		unkownstruct->member2 = &unkownstruct->member2;
		unkownstruct->member3 = &unkownstruct->member2;
		unkownstruct->member4 = &unkownstruct->member2;


/*
 * RtlNumberGenericTableElements		P173
 */
	push ebp
	mov ebp, esp
	mov eax, dword ptr [epb+8]		# 第一个参数 --> eax
	mov eax, dword ptr [eax+14]		# 返回的数据在偏移地址 +14 处
	pop ebp
	ret 4


/*
 * RtlIsGenericTableEmpty				P174
 */
	push ebp
	mov ebp, esp
	mov ecx, dword ptr [ebp+8]
	xor eax, eax
	cmp dword ptr [ecx], eax	# 比较了该数据结构的第一个成员(在偏移地址 +0 处)与EAX
	sete al
	pop ebp
	ret 4

	这个函数所做的就是检查该数据结构中偏移地址 +0 处是不是 0 。
	可知在偏移地址 +0 处有一个重要的成员，只要table中有元素这个成员就总是非零。

/*
 * RtlGetElementGenericTalbe			P175
 */
			push ebp
			mov ebp, esp
			mov ecx, dword ptr [ebp+8]	# 函数将根据table指针加载到ecx中
			mov edx, dword ptr [ecx+14] # 然后将存储在偏移地址 +14 中的值存入 EDX 中
			mov eax, dword ptr [ecx+c]	# 将偏移地址 +0c 处的第三个指针（三指针组）加载到 EAX 中
			push ebx
			push esi
			mov esi, dword ptr [ecx+10]	# 将数据结构偏移地址+10处的值装入ESI
			push edi					# EDI 入堆栈（为了把它腾出来用于存放其他值）
			mov edi, dword ptr [ebp+c]	# EBP+C 指向的值装入EDI, 也就是第二个参数
			cmp edi, -1					# 将第二个参数与-1比较
			lea ebx, dword ptr [edi+1]
			je short ntdll.7c962559
			cmp ebx, edx
			ja short ntdll.7c962559
			cmp esi, ebx
			je short ntdll.7c962554
			jbe short ntdll.7c96252b
			mov edx, esi
			shr edx, 1
			cmp ebx, edx
			jbe short ntdll.7c96251b
			sub esi, ebx
7c962511	je short ntdll.7c96254e
7c962513	dec esi
			mov eax, dword ptr [eax+4]
			jnz short ntdll.7c962513
			jmp short ntdll.7c96254e
7c96251B	test ebx, ebx
			lea eax, dword ptr [ecs+4]
			je short ntdll.7c96254e
			mov edx, ebx
7c962524	dec edx
			mov eax, dword ptr [eax]
			jnz short ntdll.7c962524
			jmp short ntdll.7c96254e
7c96252B	mov edi, ebx
7c96252D	sub edx, ebx
7c96252F	sub edi, esi
7c962531	inc edx
7c962532	cmp edi, edx
			ja short ntdll.7c962541
			test edi, edi
			je short ntdll.7c96254e
7c96253A	dec edi
7c96253B	mov eax, dword ptr [eax]
7c96253D	jnz short ntdll.7c96253A
7c96253F	jmp short ntdll.7c96254e
7c962541	test edx, edx
7c962543	lea eax, dword ptr [ecx+4]
7c962546	je short ntdll.7c96254e
7c962548	dec edx
7c962549	mov eax dword ptr [eax+4]
7c96254C	jnz short ntdll.7c962548
7c96254E	mov dword ptr [ecx+c], eax
7c962551	mov dword ptr [ecx+10], ebx
7c962554	add eax, 0c						# eax = eax+12
7c962557	jmp short ntdll.7c96255b		# 无条件跳转
7c962559	xor eax, eax
7c96255B	pop edi
			pop esi
			pop ebx
			pop ebp
			rret 8

1.
	mov ecx, dword ptr [ebp+8]
	mov edx, dword ptr [ecx+14]
	mov eax, dword ptr [ecx+c]

	函数将根据table指针加载到ecx中，然后将存储在偏移地址 +14 中的值存入 EDX 中。
	分析RtlNumberGenericTableElements时发现偏移地址 +14 处是 table 中元素的总个数。
	接下来的一条指令将偏移地址 +0c 处的第三个指针（三指针组）加载到 EAX 中。

2.
	push ebx
	push esi
	mov esi, dword ptr [ecx+10]	# 将数据结构偏移地址+10处的值装入ESI
	push edi					# EDI 入堆栈（为了把它腾出来用于存放其他值）
	mov edi, dword ptr [ebp+c]	# EBP+C 指向的值装入EDI, 也就是第二个参数
	cmp edi, -1					# 将第二个参数与-1比较，这是典型的"交错代码"的情况。
	lea ebx, dword ptr [edi+1]	# 既保持EDI中的值不变，又得到EDI加1的结果，所以没有用INC EDI，而是用LEA。
								# LEA 将EDI加1后结果放在EBX中，EDI中原来的值保持不变。
								# EBX=EDI+1
	je short ntdll.7c962559		# 如果 EDI==1，则跳转到ntdll.7c962559，为某种失败或错误条件的处理代码。
	cmp ebx, edx				# EBX=EDI+1 与 EDX 比较。
	ja short ntdll.7c962559		# 如果EBX大于EDX则跳转到ntdll.7c962559，两个测试条件而已。
								# 使用JA，表示EDX和EBX中的值都是被当作无符号数处理的。
								# 有符号数用JG指令

	/* 交错代码 P178 */
	交错代码在为现代IA-32处理生成的代码中是非常普遍的现象。
	交错代码是指令在代码中并不是按照其本来的顺序排列的，而是相互独立的指令交互在一起。
	这样在运行时CPU在必须执行第二条指令之前就有时间完成第一条指令。
	在这个例子中，也可以说代码是交错的，因为CMP指令后面没有紧跟着条件跳转指令。
	这样做是为了在执行中最大程序地实现并行。

3.
	cmp esi, ebx				# ESI=table数据结构偏移地址+10 处
	je short ntdll.7c962554		# ESI == EBX
	jbe short ntdll.7c96252b	# ESI <= EBX
								# 使用JBE。因为这里的相等条件是不可能成立的，否则第一个跳转JE就被执行了。
	mov edx, esi
	shr edx, 1
	cmp ebx, edx
	jbe short ntdll.7c96251b
	sub esi, ebx
	je short ntdll.7c96254e

	
/*
 * RtlInsertElementGenericTalbe		P190
 */
	7c924dc0	push ebp
	7c924dc1	mov ebp, esp					# 前两条的作用是创建堆栈帧
				push edi						# 将edi的内容压入堆栈
				mov edi, dword ptr [ebp+8]		# 第一个参数的值写入EDI(用作局部变量)
												# [ebp+8] 是指传给函数的第一个参数地址
				lea eax, dword ptr [ebp+8]		# 把指向的第一个参数的指针存入EAX。
				push eax
				push dword ptr [ebp+0xc]
				call ntdll.7c92147B
				push eax
				push dword ptr [ebp+8]
				push dword ptr [ebp+14]
				push dword ptr [ebp+10]
				push dword ptr [ebp+0xc]
				push edi
				call ntdll.7c924df0
				pop edi
				pop ebp
				ret 10
	
1.push指令不外乎以下三种情况:
	a.保存一个寄存器的值到堆栈中，用作函数的局部变量。在这个函数的结尾处会将这个值弹出堆栈。
	  如果是这种情况的话其代码很容易辩认，因为在压入堆栈的时候是哪个寄存器的值必须在弹出的时候还交给这个寄存器(即要反序出栈);

	b.在进行函数调用之前要把参数压入堆栈；

	c.在拷贝数值的时候，在时会在push指令后面紧跟一条pop指令，用于将这个值加载到其他的寄存器中。
	  这样的指令序列比较少见，但是某些编译器时常会产生这种代码。

2.
	mov edi, dword ptr [ebp+8]		# 第一个参数的值写入EDI(用作局部变量)
									# [ebp+8] 是指传给函数的第一个参数地址
	lea eax, dword ptr [ebp+8]		# 把指向的第一个参数的指针存入EAX。

	/* mov 与 lea 区别 */
	mov 实际上是把内存地址[ebp+8]处的值取出来。
	lea 只是计算出 ebp+8 的结果，并把结果写入 eax 。

	EAX是不是像EDI一样是另一个局部变量呢？
	为了回答这个问题，先看一下接下来的代码:
	
3.
	push eax
	push dword ptr [ebp+0xc]
	call ntdll.7c92147B

	压入堆栈的第一个参数是EAX的值，给人第一感觉EAX并非用作一个局部变量，而是编译器把它用作一个临时存储单元，
	这是因为要把第一个参数的指针压入堆栈需要用两条指令来完成。

	这是汇编语言中是一种很常见的限制条件:大多数指令都不像LEA和MOV指令那样能够接收比较复杂的参数。
	因此，编译器就只能使用MOV或LEA指令，并将MOV或LEA指令的输出存储到一个寄存器中，之后接下来的指令就可以使用该寄存器了。
	
/*
 * RtlLocateNodeGenericTable		P192
 */
	7c92147B	mov edi, edi
				push ebp
				mov ebp, esp
				push esi
				mov esi, dword ptr [edi]
				test esi, esi
				je ntdll.7c924e8c
				lea eax, dword ptr [esi+18]
				push eax
				push dword ptr [ebp+8]
				push edi
				call dword ptr [edi+18]
				test eax, eax
				je ntdll.7c924f14
				cmp eax, 1
				jnz short ntdll.7c9214bb
				mov eax, dword ptr [esi+8]
				test eax, eax
				je ntdll.7c924f14
				cmp eax, 1
				jnz short ntdll.7c9214bb
				mov eax, dword ptr [esi+8]
				test eax, eax
				jnz ntdll.7c924f22
				push 3
				pop eax
				mov ecx, dword ptr [ebp+c]
				mov dword ptr [ecx], esi
				pop esi
				pop ebp
				ret 8
				xor eax, eax
				inc eax
				jmp short ntdll.7c9214b1



1.
	mov edi, edi

	这句代码什么也没有做。
	它实际上只是被编译器当作占位符(placeholder)放在这里的一条空代码(dead code),以防有人想捕获(trap)这个函数。

	这里所说的“捕获”是指：某些外部组件增加一条JMP指令，只要被捕获函数被调用就将这条JMP指令用作通知信号。

2.
	mov esi, dword ptr [edi]	# edi 被当作一个指向某个地方的指针来用
	test esi, esi				# 测试了第一个成员(地址是offset+0)是不是0
	je ntdll.7c924e8c			# 如果是0就跳转


/*
 *  回调	P195
 */
	7c92148b	lea eax, dword ptr [esi+18]
				push eax
				push dword ptr [ebp+8]
				push edi
				call dword ptr [edi+18]		# 不是硬编码，应是一个回调函数，前面三个push是三个参数
				test eax, eax
				je ntdll.7c924f14
				cmp eax, 1
				jnz short ntdll.7c9214bb

	前面的五条指令是用来调用同一个函数的部分，但是要注意一下所调用的"地址"。
	这个地址不是常见到硬编码地址，而是由"EDI+18"这个偏移地址处的值给出的。
	这就揭示了在根table数据结构中偏移地址+18处存放着另一个成员---大概是一个回调函数。

	这个在"EDI+18"处的函数好像有三个参数: 
		1.table数据结构。
		2.传给当前函数(RtlLocateNodeGenericTalbe)的第二个参数。
		3.是"EDI+18"

		ESI寄存器在前边已经加载了根数据结构在偏移地址+0处的值，这表明偏移地址+0处包含了某种其他类型的数据结构，而且回调函数
		获得的是指向该数据结构中偏移地址+18处的指针。
	
	当这个回调函数返回之后，可以测试它的返回值，如果返回值为零的话，将跳到ntdll.7c924f14。

	同样，ntdll.7c924f14也是一个在RtlLocateNodeGenericTable函数体之外的地址。

	7c924f14	mov eax, dword ptr [esi+4]
	7c924f17	test eax, eax
	7c924f19	jnz short ntdll.7c924f22
	7c924f1B	push 2
	7c924f1D	jmp ntdll.7c9214b0
	7c924f22	mov esi, eax
	7c924f24	jmp ntdll.7c92148b

	这段代码将这个未知的数据结构中偏移地址为 +4 处的值加载到了EAX，然后测试它是不是0。
	如果不是 0 , ntdll.7c924f22。 在ntdll.7c924f22 只执行了一条指令后就又跳转到了ntdll.7c92148b(即RtlLocateNodeGenericTable体)，
	但是这个跳转是在把这个未知的数据结构(当前它存储在EAX中)中偏移地址 +4 处的值加载到ESI之后执行的。

	如果这个未知的数据结构中偏移地址 +4 处的值是 0 ， 代码则往堆栈中压入数字 2 ，然后执行短跳转到ntdll.7c9214b0,
	ntdll.7c9214b0 是 RtlLocateNodeGenericTable 函数的函数体中的一个地址。
	
	实际上，对这个未知的数据结构中偏移地址为 +4 处的值作测试的结果只有两种:
	如果它的值是 0 ，则函数就返回主调函数 RtlInserElementGenericTable(ntdll.7c9214b0 就在这个函数的结尾部分);
	如果它的值不是 0 ， 函数将把它的值装入ESI，然后跳转回ntdll.7c92148b，这就又回到刚才分析过的回调调用代码了。

	看看如果回调函数返回一个非零值会怎么样。

	7C92149E	cmp eax, 1
				jnz short ntdll.7c9214bb
				mov eax, dword ptr [esi+8]
				test eax, eax
				jnz ntdll.7c924f22
				push 3
				pop eax
				mov exc, dword ptr [ebp+c]
				mov dword ptr [ecx], esi
				pop esi
				pop ebp
				ret 8
		
		首先，好像回调函数返回的是某种类型的数而不是一个指针，也有可能返回的是一个boolean的值，但还是无法确认。
		第一个检查测试了"retrun value != 1", 并在条件不满足的情况下将偏移地址 +8 处的值加载到EAX中。

		然后测试赋给EAX的这个值是不是等于 0 ， 如果=0 ， EAX的值置为 3 (前面讨论过的push-pop方法)，其后执行的显然就是函数
		返回前做收尾代码了。
		
		看到了当调函数返回0或者返回1时代码的执行情况了。而当回调函数返回一个其他值的时候，
		代码就会通过前面看到的条跳转语句跑转到ntdll.7c9214BB继续执行。

		7c9214BB	xor eax, eax
					inc eax
					jmp short ntdll.7c9214b1

		这段代码把EAX置1， 然后跳转回ntdll.7c9214b1。此处的代码不会影响EAX，所以这实际上就是向主调函数返回1。

/*
 * RtlRealInsertElementWorker		P200
 */
	7c924df0	mov edi, edi
				push ebp
				mov ebp, esp
				cmp dword ptr [ebp+1c], 1
				push ebx
				push esi
				push edi
				je ntdll.7c935d5d
				mov edi, dword ptr [ebp+10]
				mov esi, dword ptr [ebp+8]
				mov eax, dword ptr [edi+18]
				push eax
				push esi
				call dword ptr [esi+1c]
				mov ebx, eax
				test ebx, ebx
				je ntdll.7c94d4be
				and dword ptr [ebx+4], 0
				and dword ptr [ebx+8], 0
				mov dword ptr [ebx], ebx
				lea ecx, dword ptr [esi+4]
				mov edx, dword ptr [ecx+4]
				lea eax, dword ptr [ebx+c]
				mov dword ptr [eax], ecx
				mov dword ptr [eax+4], edx
				mov dwrod ptr [edx], eax
				mov dword ptr [ecx+4], eax
				inc dword ptr [esi+14]
				cmp dword ptr [ebp+1c], 0
				je short ntdll.7c924e88
				cmp dowrd ptr [ebp+1c], 2
				mov eax, dword ptr [ebp+18]
				je ntdll.7c924f0c
				mov dword ptr [eax+8], ebx
				mov dword ptr [ebx], eax
				mov esi, dword ptr [ebp+c]
				mov ecx, edi
				mov eax, ecx
				shr ecx, 2
				lea edi, dword ptr [ebx+18]
				rep movs dword ptr es:[edi], dword ptr [esi]
				mov ecx, eax
				and ecx, 3
				rep movs byte ptr es:[edi], byte ptr [esi]
				push ebx
				call ntdll.rtlSplay
				mov ecx, dword ptr [ebp+8]
				mov dword ptr [ecx], eax
				mov eax, dword ptr [ebp+14]
				test eax, eax
				jnz ntdll.7c935d4f
				lea eax, dword ptr [ebx+18]
				pop edi
				pop esi
				pop ebx
				pop ebp
				ret 18
				mov dword ptr [esi], ebx
				jmp short ntdll.7c924e52
				xor eax, eax
				jmp ntdll.7c9214b6


/*
 * 拷贝元素		P207
 */


/*
 * 展开 table	P207
 */

/*
 * 伸展树		P209
 */
	RtlSplay

/*
 * RtlLookupElementGenericTable		P210
 */
	7c9215BB	push ebp
				mov ebp, esp
				lea eax, dword ptr [ebp+c]
				push eax
				lea eax, dword ptr [ebp+8]
				push eax
				push dword ptr [ebp+c]
				push dword ptr [ebp+8]
				call ntdll.7c9215da
				pop ebp
				ret 8
			
	执行二叉树搜索。


/*
 * 结论		P219
 */
	做逆向工程就应该站在高级语言的角度去认识低级语言代码。
	如果注意力总是忙于在各个寄存器与内存中的每个字节之间穿梭，永远不可能取得实质性的进展。

	逆向的秘诀在于:应该从整体系统着眼认识所研究的对象，始终保持大局观，随着逆向的不断深入，所研究的对象就会逐渐现出"原型"来。









